/*
* Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.errai.jpa.client.local;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.CollectionAttribute;
import javax.persistence.metamodel.ListAttribute;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.MapAttribute;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.SetAttribute;
import javax.persistence.metamodel.SingularAttribute;
import org.jboss.errai.common.client.api.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONNull;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONValue;
/**
* Errai implementation of the JPA ManagedType metamodel interface. Defines the
* attributes common to all managed types (which are entity, mapped superclass,
* and embeddable types).
*
* @author Jonathan Fuerth <jfuerth@redhat.com>
*
* @param <X>
* The actual type described by this metatype.
*/
public abstract class ErraiManagedType<X> implements ManagedType<X> {
private final Set<SingularAttribute<? super X, ?>> singularAttributes = new HashSet<SingularAttribute<? super X,?>>();
private final Set<PluralAttribute<? super X, ?, ?>> pluralAttributes = new HashSet<PluralAttribute<? super X, ?, ?>>();
protected final Class<X> javaType;
private Collection<ErraiManagedType<X>> subtypes = new HashSet<ErraiManagedType<X>>();
private final Logger logger;
public ErraiManagedType(Class<X> javaType) {
this.javaType = javaType;
this.logger = LoggerFactory.getLogger(ErraiManagedType.class);
}
public <Y> void addAttribute(Attribute<X, Y> attribute) {
if (attribute instanceof SingularAttribute) {
SingularAttribute<? super X, ?> sa = (SingularAttribute<? super X, ?>) attribute;
singularAttributes.add(sa);
}
else if (attribute instanceof PluralAttribute) {
@SuppressWarnings("unchecked")
PluralAttribute<? super X, ?, ?> pa = (PluralAttribute<? super X, ?, ?>) attribute;
pluralAttributes.add(pa);
}
else {
assert (false) : "Unknown attribute type " + attribute;
}
}
/**
* Creates and returns a new instance of the represented type. This
* implementation always throws an exception; subclasses that represent
* instantiable types should override this method with one that creates a new
* instance of that type.
*
* @return a new instance of type X.
* @throws UnsupportedOperationException
* if the represented type is abstract.
*/
public X newInstance() {
// subclasses override when this operation is possible
throw new RuntimeException("Can't create an instance of " + getJavaType().getName());
}
/**
* Returns true if this managed type represents the same Java class or a
* superclass of the given type.
*
* @param other
* the ManagedType to check
* @return true if the Java type of this managed is a superclass of the Java
* type of the other managed type.
*/
public boolean isSuperclassOf(ManagedType<?> other) {
Class<?> myClass = getJavaType();
Class<?> otherClass = other.getJavaType();
while (otherClass != null) {
if (myClass == otherClass) {
return true;
}
otherClass = otherClass.getSuperclass();
}
return false;
}
/**
* Returns the collection of entity types that are subclasses of this managed
* type. The returned collection includes this type itself (the trivial
* subtype)!
*
* @return
*/
public Collection<ErraiManagedType<X>> getSubtypes() {
return subtypes;
}
/**
* Only intended for use by the generated code that bootstraps Errai JPA.
*/
@SuppressWarnings("unchecked")
void addSubtype(ErraiManagedType<? extends X> subtype) {
subtypes.add((ErraiManagedType<X>) subtype);
}
/**
* Converts the given JSONValue, which represents an instance of this entity
* type, into the actual instance of this entity type that exists in the given
* EntityManager's persistence context. References to other identifiable
* objects are recursively retrieved from the EntityManager.
*
* @param em
* The EntityManager that owns this entity type and houses the
* persistence context.
* @param jsonValue
* A value that represents an instance of this entity type.
* @return A managed entity that is in the given EntityManager's persistence
* context.
*/
public abstract X fromJson(EntityManager em, JSONValue jsonValue);
public JSONValue toJson(EntityManager em, X sourceEntity) {
final ErraiEntityManager eem = (ErraiEntityManager) em;
JSONObject jsonValue = new JSONObject();
for (Attribute<? super X, ?> a : getAttributes()) {
ErraiAttribute<? super X, ?> attr = (ErraiAttribute<? super X, ?>) a;
switch (attr.getPersistentAttributeType()) {
case ELEMENT_COLLECTION:
case EMBEDDED:
case BASIC:
jsonValue.put(attr.getName(), makeInlineJson(sourceEntity, attr, eem));
break;
case MANY_TO_MANY:
case MANY_TO_ONE:
case ONE_TO_MANY:
case ONE_TO_ONE:
JSONValue attributeValue;
if (attr instanceof ErraiSingularAttribute) {
attributeValue = makeJsonReference(sourceEntity, (ErraiSingularAttribute<? super X, ?>) attr, eem);
}
else if (attr instanceof ErraiPluralAttribute) {
attributeValue = makeJsonReference(sourceEntity, (ErraiPluralAttribute<? super X, ?, ?>) attr, eem);
}
else {
throw new PersistenceException("Unknown attribute type " + attr);
}
jsonValue.put(attr.getName(), attributeValue);
}
}
return jsonValue;
}
/**
* Copies the state of the attributes in sourceEntity into targetEntity.
* Related entities are resolved from the given entity manager before the
* state is copied.
*
* @param em
* The entity manager that sourceEntity and targetEntity exist in.
* @param targetEntity
* The entity whose attributes' state will be written to. Not null.
* @param sourceEntity
* The entity whose attributes' state will be read from. Not null.
*/
public void mergeState(ErraiEntityManager em, X targetEntity, X sourceEntity) {
for (Attribute<? super X, ?> a : getAttributes()) {
ErraiAttribute<? super X, ?> attr = (ErraiAttribute<? super X, ?>) a;
switch (attr.getPersistentAttributeType()) {
case ELEMENT_COLLECTION:
case EMBEDDED:
case BASIC:
copyAttribute(attr, targetEntity, sourceEntity);
break;
case MANY_TO_MANY:
case ONE_TO_MANY:
copyPluralAssociation(em, ((ErraiPluralAttribute<X, ?, ?>) attr), targetEntity, sourceEntity);
break;
case MANY_TO_ONE:
case ONE_TO_ONE:
copySingularAssociation(em, attr, targetEntity, sourceEntity);
break;
default:
throw new IllegalArgumentException("Attribute has unknown type: " + attr);
}
}
}
private static <X, Y> void copyAttribute(ErraiAttribute<X, Y> attr, X targetEntity, X sourceEntity) {
attr.set(targetEntity, attr.get(sourceEntity));
}
private static <X, Y> void copySingularAssociation(
ErraiEntityManager em,
ErraiAttribute<X, Y> attr,
X targetEntity,
X sourceEntity) {
ErraiIdentifiableType<Y> relatedEntityType = em.getMetamodel().entity(attr.getJavaType());
Y oldRelatedEntity = attr.get(sourceEntity);
Y resolvedEntity;
if (oldRelatedEntity == null) {
resolvedEntity = null;
}
else {
Key<Y, ?> key = em.keyFor(oldRelatedEntity);
resolvedEntity = em.find(key, Collections.<String,Object>emptyMap());
if (resolvedEntity == null) {
resolvedEntity = relatedEntityType.newInstance();
}
}
attr.set(targetEntity, resolvedEntity);
}
private static <X, C, E> void copyPluralAssociation(
ErraiEntityManager em,
ErraiPluralAttribute<X, C, E> attr,
X targetEntity,
X sourceEntity) {
C oldCollection = attr.get(sourceEntity);
C newCollection;
if (oldCollection == null) {
newCollection = null;
}
else {
newCollection = attr.createEmptyCollection();
ErraiIdentifiableType<E> elemType = em.getMetamodel().entity(attr.getElementType().getJavaType());
// TODO support map-valued plural attributes
for (Object oldEntry : (Collection<?>) oldCollection) {
Key<Object, ?> key = em.keyFor(oldEntry);
Object resolvedEntry = em.find(key, Collections.<String,Object>emptyMap());
if (resolvedEntry == null) {
resolvedEntry = elemType.newInstance();
}
((Collection) newCollection).add(resolvedEntry);
}
}
attr.set(targetEntity, newCollection);
}
/**
* Returns an inline JSON representation of the value of the given attribute
* of the given entity instance.
*
* @param targetEntity
* The instance of the entity to retrieve the attribute value from.
* Not null.
* @param attr
* The attribute to read from {@code targetEntity}. Not null.
* @param eem
* The ErraiEntityManager that owns the entity. Not null.
* @return a JSONValue that represents the requested attribute value of the
* given entity. Never null, although it could be JSONNull.
*/
private <Y> JSONValue makeInlineJson(X targetEntity, ErraiAttribute<? super X, Y> attr, ErraiEntityManager eem) {
Class<Y> attributeType = attr.getJavaType();
Y attrValue = attr.get(Assert.notNull(targetEntity));
// FIXME this should search all managed types, or maybe all embeddables. not just entities.
// TODO it would be better to code-generate an Attribute.asJson() method than to do this at runtime
if (eem.getMetamodel().getEntities().contains(attributeType)) {
ErraiIdentifiableType<Y> attrEntityType = eem.getMetamodel().entity(attributeType);
return attrEntityType.toJson(eem, attrValue);
}
return JsonUtil.basicValueToJson(attrValue);
}
protected <Y> void parseInlineJson(X targetEntity, ErraiAttribute<? super X, Y> attr, JSONValue attrJsonValue, ErraiEntityManager eem) {
Class<Y> attributeType = attr.getJavaType();
Y value;
// FIXME this should search all managed types, or maybe all embeddables. not just entities.
if (eem.getMetamodel().getEntities().contains(attributeType)) {
ErraiIdentifiableType<Y> attrEntityType = eem.getMetamodel().entity(attributeType);
value = attrEntityType.fromJson(eem, attrJsonValue);
}
else {
value = JsonUtil.basicValueFromJson(attrJsonValue, attributeType);
}
attr.set(targetEntity, value);
}
/**
* Returns a JSON object that represents a reference to the given attribute.
* The reference is done by Entity identity (the type of the attribute is
* assumed to be an entity type).
*
* @param targetEntity
* The instance of the entity to retrieve the attribute value from.
* Not null.
* @param attr
* The attribute to read from {@code targetEntity}. Not null, and
* must be an entity type.
* @param eem
* The ErraiEntityManager that owns the entity. Not null.
* @return a JSONValue that is a reference to the given attribute value. Never
* null, although it could be JSONNull.
*/
private <Y> JSONValue makeJsonReference(X targetEntity, ErraiSingularAttribute<? super X, Y> attr, ErraiEntityManager eem) {
Class<Y> attributeType = attr.getJavaType();
Y entityToReference = attr.get(targetEntity);
if (entityToReference == null) {
return JSONNull.getInstance();
}
ErraiIdentifiableType<Y> attrEntityType = eem.getMetamodel().entity(attributeType);
if (attrEntityType == null) {
throw new IllegalArgumentException("Can't make a reference to non-entity-typed attribute " + attr);
}
Object idToReference = attrEntityType.getId(Object.class).get(entityToReference);
JSONValue ref;
if (idToReference == null) {
ref = JSONNull.getInstance();
}
else {
// XXX attrEntityType is incorrect entityToReference is a subtype of attr.getJavaType()
ref = new Key<Y, Object>(attrEntityType, idToReference).toJsonObject();
}
return ref;
}
/**
* Returns a JSON object that represents a reference to the given attribute.
* The reference is done by Entity identity (the type of the attribute is
* assumed to be an entity type).
*
* @param targetEntity
* The instance of the entity to retrieve the attribute value from.
* Not null.
* @param attr
* The attribute to read from {@code targetEntity}. Not null, and
* must be an entity type.
* @param eem
* The ErraiEntityManager that owns the entity. Not null.
* @return a JSONArray that contains references to each element in the given
* attribute's collection value. Returns JSONNull if the attribute has
* a null collection.
*/
private <C, E> JSONValue makeJsonReference(X targetEntity, ErraiPluralAttribute<? super X, C, E> attr, ErraiEntityManager eem) {
// XXX when we support maps, we should use getCollection()/getMap() and this will fix the type safety warnings
C attrValue = attr.get(targetEntity);
if (attrValue == null) {
return JSONNull.getInstance();
}
Class<E> attributeType = attr.getElementType().getJavaType();
ErraiIdentifiableType<E> attrEntityType = eem.getMetamodel().entity(attributeType);
if (attrEntityType == null) {
throw new IllegalArgumentException("Can't make a reference to collection of non-entity-typed attributes " + attr);
}
JSONArray array = new JSONArray();
int index = 0;
for (E element : (Iterable<E>) attrValue) {
Object idToReference = attrEntityType.getId(Object.class).get(element);
JSONValue ref;
if (idToReference == null) {
ref = JSONNull.getInstance();
}
else {
// XXX attrEntityType is incorrect for collection elements that are subtypes of the attrEntityType
ref = new Key<E, Object>(attrEntityType, idToReference).toJsonObject();
}
array.set(index++, ref);
}
return array;
}
protected <Y> void parseSingularJsonReference(
X targetEntity, ErraiSingularAttribute<? super X, Y> attr, JSONValue attrJsonValue, ErraiEntityManager eem) {
if (attrJsonValue == null || attrJsonValue.isNull() != null) return;
Key<Y, ?> key = (Key<Y, ?>) Key.fromJsonObject(eem, attrJsonValue.isObject(), true);
logger.trace(" looking for " + key);
Y value = eem.find(key, Collections.<String,Object>emptyMap());
attr.set(targetEntity, value);
}
protected <C, E> void parsePluralJsonReference(
X targetEntity, ErraiPluralAttribute<? super X, C, E> attr, JSONArray attrJsonValues, ErraiEntityManager eem) {
if (attrJsonValues == null || attrJsonValues.isNull() != null) return;
Class<E> attributeElementType = attr.getElementType().getJavaType();
ErraiIdentifiableType<E> attrEntityType = eem.getMetamodel().entity(attributeElementType);
// FIXME this is broken for Map attributes
// TODO when we support Map attributes, we should get the attribute with getCollection()/getMap() to fix this warning
Collection<E> collection = (Collection<E>) attr.createEmptyCollection();
for (int i = 0; i < attrJsonValues.size(); i++) {
Key<E, ?> key = (Key<E, ?>) Key.fromJsonObject(eem, attrJsonValues.get(i).isObject(), true);
logger.trace(" looking for " + key);
E value = eem.getPartiallyConstructedEntity(key);
if (value == null) {
value = eem.find(key, Collections.<String,Object>emptyMap());
}
collection.add(value);
}
attr.set(targetEntity, (C) collection);
}
// ---------- JPA API Below This Line -------------
@Override
public Set<Attribute<? super X, ?>> getAttributes() {
Set<Attribute<? super X, ?>> attributes = new HashSet<Attribute<? super X, ?>>();
attributes.addAll(singularAttributes);
attributes.addAll(pluralAttributes);
return attributes;
}
@Override
public Set<Attribute<X, ?>> getDeclaredAttributes() {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@SuppressWarnings("unchecked")
@Override
public <Y> ErraiSingularAttribute<? super X, Y> getSingularAttribute(String name,
Class<Y> type) {
for (Attribute<? super X, ?> attr : singularAttributes) {
if (attr.getName().equals(name)) {
if (attr.getJavaType() != type) {
throw new ClassCastException("Attribute \"" + name + "\" of entity " + getJavaType() +
" is not of the requested type " + type);
}
return (ErraiSingularAttribute<? super X, Y>) attr;
}
}
return null;
}
@Override
public <Y> SingularAttribute<X, Y> getDeclaredSingularAttribute(String name,
Class<Y> type) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public Set<SingularAttribute<? super X, ?>> getSingularAttributes() {
return singularAttributes;
}
@Override
public Set<SingularAttribute<X, ?>> getDeclaredSingularAttributes() {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public <E> CollectionAttribute<? super X, E> getCollection(String name,
Class<E> elementType) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public <E> CollectionAttribute<X, E> getDeclaredCollection(String name,
Class<E> elementType) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public <E> SetAttribute<? super X, E> getSet(String name, Class<E> elementType) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public <E> SetAttribute<X, E> getDeclaredSet(String name, Class<E> elementType) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public <E> ListAttribute<? super X, E> getList(String name,
Class<E> elementType) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public <E> ListAttribute<X, E> getDeclaredList(String name,
Class<E> elementType) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public <K, V> MapAttribute<? super X, K, V> getMap(String name,
Class<K> keyType, Class<V> valueType) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public <K, V> MapAttribute<X, K, V> getDeclaredMap(String name,
Class<K> keyType, Class<V> valueType) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public Set<PluralAttribute<? super X, ?, ?>> getPluralAttributes() {
return pluralAttributes;
}
@Override
public Set<PluralAttribute<X, ?, ?>> getDeclaredPluralAttributes() {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public ErraiAttribute<? super X, ?> getAttribute(String name) {
// XXX would be better to keep attributes in a map
for (Attribute<? super X, ?> attr : singularAttributes) {
if (attr.getName().equals(name)) {
return (ErraiAttribute<? super X, ?>) attr;
}
}
for (Attribute<? super X, ?> attr : pluralAttributes) {
if (attr.getName().equals(name)) {
return (ErraiAttribute<? super X, ?>) attr;
}
}
return null;
}
@Override
public Attribute<X, ?> getDeclaredAttribute(String name) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public SingularAttribute<? super X, ?> getSingularAttribute(String name) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public SingularAttribute<X, ?> getDeclaredSingularAttribute(String name) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public CollectionAttribute<? super X, ?> getCollection(String name) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public CollectionAttribute<X, ?> getDeclaredCollection(String name) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public SetAttribute<? super X, ?> getSet(String name) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public SetAttribute<X, ?> getDeclaredSet(String name) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public ListAttribute<? super X, ?> getList(String name) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public ListAttribute<X, ?> getDeclaredList(String name) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public MapAttribute<? super X, ?, ?> getMap(String name) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public MapAttribute<X, ?, ?> getDeclaredMap(String name) {
// TODO Auto-generated method stub
throw new RuntimeException("Not implemented");
}
@Override
public Class<X> getJavaType() {
return javaType;
}
@Override
public String toString() {
return "[ManagedType " + getJavaType().getName() + "]";
}
}